<!DOCTYPE html>
<html lang="en">
<head>
  <title>Logger - Born to build better enterprise frameworks and apps</title>
  <meta charset="utf-8">
  <meta name="description" content="index.description">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <link rel="icon" href="/images/favicon.png" type="image/x-icon">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.css" />
<link rel="stylesheet" href="/css/index.css">

    <script>
    !function(t,e,a,r,c){t.TracertCmdCache=t.TracertCmdCache||[],t[c]=window[c]||
      {_isInit:!0,call:function(){t.TracertCmdCache.push(arguments)},
      start:function(t){this.call('start',t)}},t[c].l=new Date;
      var n=e.createElement(a),s=e.getElementsByTagName(a)[0];
      n.async=!0,n.src=r,s.parentNode.insertBefore(n,s)}
    (window,document,'script','https://tracert.alipay.com/tracert.js','Tracert');
      Tracert.start({
        plugins: [ 'BucName' ],
        spmAPos: 'a454',
        spmBPos: 'b4893',
      });
    </script>
  
</head>
<body>
  <div class="nav" >
  <header>
    <a href="/en/" class="nav-logo leftpadding" alt="egg"><img src="https://zos.alipayobjects.com/rmsportal/VTcUYAaoKqXyHJbLAPyF.svg"></a>
    <ul class="nav-item">
      <li>
        <form id="search-form">
          <input type="text" id="search-query" class="search-query st-default-search-input">
        </form>
      </li>
      <li><a href="/en/intro/" alt="Guide">Guide</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/en/tutorials/index.html" alt="Tutorials">Tutorials</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="Plugins">Plugins</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="Release">Release</a></li>
      
      
        <li class="translations">
          <a class="nav-link">中文文档</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/core/logger.html" style="color: #22ab28">English</a></li><li><a id="zh-cn" href="/zh-cn/core/logger.html" >中文</a></li></ul>
        </li>
      
      <li><iframe src="https://ghbtns.com/github-btn.html?user=eggjs&repo=egg&type=star&count=true" frameborder="0" scrolling="0" width="150px" height="20px"></iframe></li>
    </ul>
    <a id="mobileTrigger" href="#" class="mobile-trigger">
      <ul>
        <li></li>
        <li></li>
        <li></li>
      </ul>
    </a>
  </header>
</div>
  <div id="container" class="container">
    <div class="page-main">
  <article class="markdown-body">
    <h1>Logger</h1>
    <p>There is no doubt that logs are important part for monitoring application and debugging in Web development.</p>
<p>Built-in enterprise scaled logger, <a href="https://github.com/eggjs/egg-logger" target="_blank" rel="noopener">egg-logger</a>, makes developer implement logging more easily than ever before.</p>
<p>Core features:</p>
<ul>
<li>Levels</li>
<li>Universal logging, <code>.error()</code> will save <code>ERROR</code> level logs into a file for later debugging</li>
<li>Logs from dispatch and runtime are separated</li>
<li>Create your logger</li>
<li>Multi-process logs</li>
<li>Automatic sharding</li>
<li>High Performance</li>
</ul>
<h2 id="location"><a class="markdown-anchor" href="#location">#</a> Location</h2>
<ul>
<li>Log files are located in <code>${appInfo.root}/logs/${appInfo.name}</code> by default. For instance, <code>/home/admin/logs/example-app</code>.</li>
<li>To avoid conflicts between environments and provide a more convenience way to manage logs, log files will be written into <code>logs</code> directory. For instance, <code>/path/to/example-app/logs/example-app</code>.</li>
</ul>
<p>Change <code>dir</code> in logger:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.$&#123;env&#125;.js</span></span><br><span class="line">exports.logger = &#123;</span><br><span class="line">  dir: <span class="string">'/path/to/your/custom/log/dir'</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="type-of-logs"><a class="markdown-anchor" href="#type-of-logs">#</a> Type of Logs</h2>
<p>Egg offers a few loggers for different scenarios:</p>
<ul>
<li>appLogger <code>${appInfo.name}-web.log</code>，for example <code>example-app-web.log</code> stores logs from application, it will be used in general.</li>
<li>coreLogger <code>egg-web.log</code>, logs from Egg's core and plugin.</li>
<li>errorLogger <code>common-error.log</code> should not be invoked directly. However, <code>.error()</code> in every logger will redirect logs to it for debugging.</li>
<li>agentLogger <code>egg-agent.log</code>, logs from agent process.</li>
</ul>
<p>If you want to change names of above loggers, you can override them in config:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.$&#123;env&#125;.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">appInfo</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    logger: &#123;</span><br><span class="line">      appLogName: <span class="string">`<span class="subst">$&#123;appInfo.name&#125;</span>-web.log`</span>,</span><br><span class="line">      coreLogName: <span class="string">'egg-web.log'</span>,</span><br><span class="line">      agentLogName: <span class="string">'egg-agent.log'</span>,</span><br><span class="line">      errorLogName: <span class="string">'common-error.log'</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="printing"><a class="markdown-anchor" href="#printing">#</a> Printing</h2>
<h3 id="context-logger"><a class="markdown-anchor" href="#context-logger">#</a> Context Logger</h3>
<p>It's proper to log details in requests with context logger. The logger will append basics about requests to each log. For example, <code>[$userId/$ip/$traceId/${cost}ms $method $url]</code>.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.logger.debug(<span class="string">'debug info'</span>);</span><br><span class="line">ctx.logger.info(<span class="string">'some request data: %j'</span>, ctx.request.body);</span><br><span class="line">ctx.logger.warn(<span class="string">'WARNING!!!!'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// .error will save information in call stack into errorLog file.</span></span><br><span class="line"><span class="comment">// Exceptions must be guaranteed to be Error or object extended from Error, which offers a trace of what functions were called.</span></span><br><span class="line">ctx.logger.error(<span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'whoops'</span>));</span><br></pre></td></tr></table></figure>
<p>For developers who create frameworks or plugins, <code>ctx.coreLogger</code> is another option in Context Logger.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.coreLogger.info(<span class="string">'info'</span>);</span><br></pre></td></tr></table></figure>
<h3 id="app-logger"><a class="markdown-anchor" href="#app-logger">#</a> App Logger</h3>
<p>For developers who want to know more details about dispatch in Egg, they can easily use <code>App Logger</code> to make that happen:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">app</span> =&gt;</span> &#123;</span><br><span class="line">  app.logger.debug(<span class="string">'debug info'</span>);</span><br><span class="line">  app.logger.info(<span class="string">'Latency: %d ms'</span>, <span class="built_in">Date</span>.now() - start);</span><br><span class="line">  app.logger.warn(<span class="string">'warning!'</span>);</span><br><span class="line"></span><br><span class="line">  app.logger.error(someErrorObj);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p><code>app.coreLogger</code> in app is similar to <code>ctx.coreLogger</code> in context:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">app</span> =&gt;</span> &#123;</span><br><span class="line">  app.coreLogger.info(<span class="string">'Latency: %d ms'</span>, <span class="built_in">Date</span>.now() - start);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h3 id="agent-logger"><a class="markdown-anchor" href="#agent-logger">#</a> Agent Logger</h3>
<p>Agent also supports <code>agent.coreLogger</code> as the same feature to context and app above.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// agent.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">agent</span> =&gt;</span> &#123;</span><br><span class="line">  agent.logger.debug(<span class="string">'debug info'</span>);</span><br><span class="line">  agent.logger.info(<span class="string">'Latency: %d ms'</span>, <span class="built_in">Date</span>.now() - start);</span><br><span class="line">  agent.logger.warn(<span class="string">'warning!'</span>);</span><br><span class="line"></span><br><span class="line">  agent.logger.error(someErrorObj);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>For more about Agent, you can take a look at <a href="./cluster-and-ipc.html">Multi-process</a>.</p>
<h2 id="encoding"><a class="markdown-anchor" href="#encoding">#</a> Encoding</h2>
<p>The default encoding setting(<code>utf-8</code>) can be changed via <code>encoding</code> in config:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.$&#123;env&#125;.js</span></span><br><span class="line">exports.logger = &#123;</span><br><span class="line">  encoding: <span class="string">'gbk'</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="log-level"><a class="markdown-anchor" href="#log-level">#</a> Log Level</h2>
<p>Logs are designed in 5 levels, including <code>NONE</code>, <code>DEBUG</code>, <code>INFO</code>, <code>WARN</code> and <code>ERROR</code>. For inspecting in development, they will also be written into files and printed into terminal as well.</p>
<h3 id="levels"><a class="markdown-anchor" href="#levels">#</a> Levels</h3>
<p>Generally, Egg will only write logs in levels higher than <code>INFO</code>, so it means that <code>NONE</code> and <code>DEBUG</code> information will be lost in files.</p>
<p>If you want to change the level of the logger, you can make it as follow:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.$&#123;env&#125;.js</span></span><br><span class="line">exports.logger = &#123;</span><br><span class="line">  level: <span class="string">'DEBUG'</span>, <span class="comment">// logs in all level will be written into files</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>Stop writing logs in all levels:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.$&#123;env&#125;.js</span></span><br><span class="line">exports.logger = &#123;</span><br><span class="line">  level: <span class="string">'NONE'</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h3 id="in-terminal"><a class="markdown-anchor" href="#in-terminal">#</a> In terminal</h3>
<p>By default, Egg will only print out <code>INFO</code>, <code>WARN</code> and <code>ERROR</code> in terminal. <code>logger.consoleLevel</code>(default: <code>INFO</code>) is defined as the logger level in terminal. Similarly, it can be changed as following:</p>
<p>Print logs in all levels:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.$&#123;env&#125;.js</span></span><br><span class="line">exports.logger = &#123;</span><br><span class="line">  consoleLevel: <span class="string">'DEBUG'</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>Stop printing logs in all levels:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.$&#123;env&#125;.js</span></span><br><span class="line">exports.logger = &#123;</span><br><span class="line">  consoleLevel: <span class="string">'NONE'</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="create-your-logger"><a class="markdown-anchor" href="#create-your-logger">#</a> Create your logger</h2>
<h3 id="customized"><a class="markdown-anchor" href="#customized">#</a> Customized</h3>
<p>For common scenarios, <strong>it's unnecessary to create new logger</strong>, because too many loggers will make them hard to be managed for later debugging.</p>
<p>The logger you create can be declared in config:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.$&#123;env&#125;.js</span></span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">appInfo</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    customLogger: &#123;</span><br><span class="line">      xxLogger: &#123;</span><br><span class="line">        file: path.join(appInfo.root, <span class="string">'logs/xx.log'</span>),</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>Now, you can get loggers via <code>app.getLogger('xxLogger')</code> or <code>ctx.getLogger('xxLogger')</code>, and the logs printed from those loggers are similar to the ones from <code>coreLogger</code>.</p>
<h3 id="advanced"><a class="markdown-anchor" href="#advanced">#</a> Advanced</h3>
<p>Logs will be written into files by default. Further, they will also be printed into terminal in development. But what if we need to print those into another place? Creating customized transport can take you there.</p>
<p>Transport can be considered as a tunnel to transfer data in Egg. A logger contains multiple transports, for example the one by default contains <code>fileTransport</code> and <code>consoleTransport</code>.</p>
<p>For concrete scenario, we take <code>common-error.log</code> as an example, which not only printed into files, but also sent to another remote service. At first, we can create a new transport for sending logs to remote:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> co = <span class="built_in">require</span>(<span class="string">'co'</span>);</span><br><span class="line"><span class="keyword">const</span> util = <span class="built_in">require</span>(<span class="string">'util'</span>);</span><br><span class="line"><span class="keyword">const</span> Transport = <span class="built_in">require</span>(<span class="string">'egg-logger'</span>).Transport;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">RemoteErrorTransport</span> <span class="keyword">extends</span> <span class="title">Transport</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Create log() to upload logs</span></span><br><span class="line">  log(level, args) &#123;</span><br><span class="line">    <span class="keyword">let</span> log;</span><br><span class="line">    <span class="keyword">if</span> (args[<span class="number">0</span>] <span class="keyword">instanceof</span> <span class="built_in">Error</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> err = args[<span class="number">0</span>];</span><br><span class="line">      log = util.format(<span class="string">'%s: %s\n%s\npid: %s\n'</span>, err.name, err.message, err.stack, process.pid);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      log = util.format(...args);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">this</span>.options.app.curl(<span class="string">'http://url/to/remote/error/log/service/logs'</span>, &#123;</span><br><span class="line">      data: log,</span><br><span class="line">      method: <span class="string">'POST'</span>,</span><br><span class="line">    &#125;).catch(<span class="built_in">console</span>.error);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Transport attached to errorLogger in app.js, makes logs sync to it once those are created.</span></span><br><span class="line">app.getLogger(<span class="string">'errorLogger'</span>).set(<span class="string">'remote'</span>, <span class="keyword">new</span> RemoteErrorTransport(&#123; <span class="attr">level</span>: <span class="string">'ERROR'</span>, app &#125;));</span><br></pre></td></tr></table></figure>
<p>Performance is what we always consider as important part in our services so that logs will firstly be written into memory and transferred to remote later.</p>
<h2 id="log-sharding"><a class="markdown-anchor" href="#log-sharding">#</a> Log Sharding</h2>
<p>One common requirement you can find in enterprise logs is automatic log sharding, which offers a convenient way for management. Luckily, Egg takes <a href="https://github.com/eggjs/egg-logrotator" target="_blank" rel="noopener">egg-logrotator</a> as built-in solution to meet the need.</p>
<h3 id="daily-sharding"><a class="markdown-anchor" href="#daily-sharding">#</a> Daily Sharding</h3>
<p>This is the default way in Egg to cut the logs into files named by <code>.log.YYYY-MM-DD</code> at every <code>00:00</code>. For example, <code>example-app-web.log</code> will be cut into files as follow, <code>example-app-web.log.YYYY-MM-DD</code>.</p>
<h3 id="size-sharding"><a class="markdown-anchor" href="#size-sharding">#</a> Size Sharding</h3>
<p>The log file also can be cut into ones by size. For example, Egg will process <code>egg-web.log</code> when its size reach 2G:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.$&#123;env&#125;.js</span></span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">appInfo</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    logrotator: &#123;</span><br><span class="line">      filesRotateBySize: [</span><br><span class="line">        path.join(appInfo.root, <span class="string">'logs'</span>, appInfo.name, <span class="string">'egg-web.log'</span>),</span><br><span class="line">      ],</span><br><span class="line">      maxFileSize: <span class="number">2</span> * <span class="number">1024</span> * <span class="number">1024</span> * <span class="number">1024</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>Logs written into <code>filesRotateBySize</code> file will never be processed again by date.</p>
<h3 id="hourly-sharding"><a class="markdown-anchor" href="#hourly-sharding">#</a> Hourly Sharding</h3>
<p>There is another option that the log files can be divided into small ones by hour.</p>
<p>For example, we need to cut <code>common-error.log</code> by hour just like following implementation.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.$&#123;env&#125;.js</span></span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">appInfo</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    logrotator: &#123;</span><br><span class="line">      filesRotateByHour: [</span><br><span class="line">        path.join(appInfo.root, <span class="string">'logs'</span>, appInfo.name, <span class="string">'common-error.log'</span>),</span><br><span class="line">      ],</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>Logs written into <code>filesRotateByHour</code> file will never be processed again by date.</p>
<h2 id="performance"><a class="markdown-anchor" href="#performance">#</a> Performance</h2>
<p>Generally, requests are frequent events to Web services, so writing logs into disk after each event will cause more unexpected I/O. Egg takes following strategy to write logs:</p>
<blockquote>
<p>Logs will be firstly transferred into memory, and then Egg will asynchronously write them into files by second.</p>
</blockquote>
<p>More about <a href="https://github.com/eggjs/egg-logger" target="_blank" rel="noopener">egg-logger</a> and <a href="https://github.com/eggjs/egg-logrotator" target="_blank" rel="noopener">egg-logrotator</a>。</p>

  </article>
  <aside id="mobileAside" class="toc">
  <div class="mobile-menu">
    <ul>
      <li><a href="/en/intro/" alt="Guide">Guide</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/en/tutorials/index.html" alt="Tutorials">Tutorials</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="Plugins">Plugins</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="Release">Release</a></li>
      
      
        <li class="translations">
          <a class="nav-link">中文文档</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/core/logger.html" style="color: #22ab28">English</a></li><li><a id="zh-cn" href="/zh-cn/core/logger.html" >中文</a></li></ul>
        </li>
      
    </ul>
  </div>
  <dl><dt id="title-Intro" style="cursor: pointer;" class="aside-title">Guide<a id="collapse-icon-Intro" class="icon opend"></a></dt><dd id=panel-Intro><ul><li><a href="/en/intro/index.html" class="menu-link">What is Egg?</a></li><li><a href="/en/intro/egg-and-koa.html" class="menu-link">Egg and Koa</a></li><li><a href="/en/intro/quickstart.html" class="menu-link">Quick Start</a></li><li><a href="/en/tutorials/progressive.html" class="menu-link">Progressive</a></li><li><a href="/en/migration.html" class="menu-link">Migration to 2.x</a></li></ul></dd><dt id="title-Basics" style="cursor: pointer;" class="aside-title">Basis Function<a id="collapse-icon-Basics" class="icon opend"></a></dt><dd id=panel-Basics><ul><li><a href="/en/basics/structure.html" class="menu-link">Directory Structure</a></li><li><a href="/en/basics/objects.html" class="menu-link">Built-in Objects</a></li><li><a href="/en/basics/env.html" class="menu-link">Environment</a></li><li><a href="/en/basics/config.html" class="menu-link">Configuration</a></li><li><a href="/en/basics/middleware.html" class="menu-link">Middleware</a></li><li><a href="/en/basics/router.html" class="menu-link">Router</a></li><li><a href="/en/basics/controller.html" class="menu-link">Controller</a></li><li><a href="/en/basics/service.html" class="menu-link">Service</a></li><li><a href="/en/basics/plugin.html" class="menu-link">Plugin</a></li><li><a href="/en/basics/schedule.html" class="menu-link">Schedule</a></li><li><a href="/en/basics/extend.html" class="menu-link">Extend</a></li><li><a href="/en/basics/app-start.html" class="menu-link">Custom Init</a></li></ul></dd><dt id="title-Core" style="cursor: pointer;" class="aside-title">Core<a id="collapse-icon-Core" class="icon opend"></a></dt><dd id=panel-Core><ul><li><a href="/en/core/development.html" class="menu-link">Development</a></li><li><a href="/en/core/unittest.html" class="menu-link">Unit Testing</a></li><li><a href="/en/core/deployment.html" class="menu-link">Deployment</a></li><li><a href="/en/core/logger.html" class="menu-link">Logger</a></li><li><a href="/en/core/httpclient.html" class="menu-link">HttpClient</a></li><li><a href="/en/core/cookie-and-session.html" class="menu-link">Cookie and Session</a></li><li><a href="/en/core/cluster-and-ipc.html" class="menu-link">Cluster and IPC</a></li><li><a href="/en/core/view.html" class="menu-link">View</a></li><li><a href="/en/core/error-handling.html" class="menu-link">Error Handling</a></li><li><a href="/en/core/security.html" class="menu-link">Security</a></li><li><a href="/en/core/i18n.html" class="menu-link">i18n</a></li></ul></dd><dt id="title-Tutorials" style="cursor: pointer;" class="aside-title">Tutorials<a id="collapse-icon-Tutorials" class="icon opend"></a></dt><dd id=panel-Tutorials><ul><li><a href="/en/tutorials/mysql.html" class="menu-link">MySQL</a></li><li><a href="/en/tutorials/restful.html" class="menu-link">RESTful API</a></li><li><a href="/en/tutorials/passport.html" class="menu-link">Passport</a></li><li><a href="/en/tutorials/socketio.html" class="menu-link">Socket.IO</a></li><li><a href="/en/tutorials/assets.html" class="menu-link">Assets</a></li><li><a href="/en/tutorials/typescript.html" class="menu-link">TypeScript</a></li></ul></dd><dt id="title-Advanced" style="cursor: pointer;" class="aside-title">Advanced<a id="collapse-icon-Advanced" class="icon opend"></a></dt><dd id=panel-Advanced><ul><li><a href="/en/advanced/loader.html" class="menu-link">Loader</a></li><li><a href="/en/advanced/plugin.html" class="menu-link">Plugin Development</a></li><li><a href="/en/advanced/framework.html" class="menu-link">Framework</a></li><li><a href="/en/advanced/cluster-client.html" class="menu-link">Cluster Enhancement</a></li><li><a href="/en/advanced/view-plugin.html" class="menu-link">View Plugin</a></li><li><a href="/en/style-guide.html" class="menu-link">Style Guide</a></li></ul></dd><dt id="title-Community" style="cursor: pointer;" class="aside-title">Community<a id="collapse-icon-Community" class="icon opend"></a></dt><dd id=panel-Community><ul><li><a href="/en/plugins/" class="menu-link">Plugin List</a></li><li><a href="/en/contributing.html" class="menu-link">Contributing</a></li><li><a href="/en/resource.html" class="menu-link">Resource</a></li><li><a href="/en/faq.html" class="menu-link">FAQ</a></li></ul></dd></dl>
</aside>
<script>
var mobileTrigger = document.getElementById('mobileTrigger');
var mobileAside = document.getElementById('mobileAside');

var expandMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon opend';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = '';
  }
}

var collapseMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon closed';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = 'aside-panel-hidden';
  }
}

mobileAside.onclick = function(e) {
  const targetId = e.target.id;
  if (targetId && (targetId.indexOf('title-') > -1 || targetId.indexOf('collapse-icon-') > -1)) {
    const title = targetId.replace('title-', '').replace('collapse-icon-', '');
    try { 
      // the the browser may have no localStroage or JSON.parse may throw exception.
      const menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
        
      // current menu status
      const curClosed = menuInfo[title] ? menuInfo[title].closed : false; // default false

      // change UI
      curClosed ? expandMenu(title) : collapseMenu(title);

      // save menuInfo to localStorage
      menuInfo[title] = { closed: !curClosed } // opposite
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    } catch (e) {}
  }
};

mobileTrigger.onclick = function(e) {
  e.preventDefault();
  if (mobileAside.className.indexOf('mobile-show') === -1) {
    mobileAside.className += ' mobile-show';
  } else {
    mobileAside.className = 'toc';
  }
};

(function() {
  // save data to localStorage because the page will refresh when user change the url.
  let menuInfo;
  try { 
    // the the browser may have no localStroage or JSON.parse may throw exception.
    menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
    if (!menuInfo) {
      menuInfo = {};
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    }
  } catch (e) {
    menuInfo = {}; // default {}
  }

  for (const title in menuInfo) {
    if (menuInfo[title] && menuInfo[title].closed) { // menu in closed status.
      collapseMenu(title);
    } else {
      expandMenu(title);
    }
  }

  // highlight menu
  const pathname = window.location.pathname;
  const selector = `a[href="${pathname}"].menu-link,a[href="${pathname}index.html"].menu-link`;
  const menuItem = mobileAside.querySelector(selector);
  if (menuItem) { menuItem.className += ' highlight'; }
})();
</script>

</div>

  </div>
</body>
<script src="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.js"></script>
<script>
docsearch({
  apiKey: '1561de31a86f79507ea00cdb54ce647c',
  indexName: 'eggjs',
  inputSelector: '#search-query',
});
</script>
<div class="cnzz">
<script src="https://s11.cnzz.com/z_stat.php?id=1261142226&web_id=1261142226" language="JavaScript"></script>
</div>

</html>
